package cas

import (
	"context"
	"encoding/json"
	"fmt"
	"reflect"
	"strconv"
	"strings"
	"time"

	ge "gitee.com/haifengat/gin-ex/v2"
	zd "gitee.com/haifengat/zorm-dm"
	"github.com/gin-gonic/gin"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
)

var (
	tblnameUpperEntity = map[string]zd.IEntity{}
	ctxDao             context.Context
)

// getIDs 取表名对应的 主ID
//
//	@param tblName
//	@return ids
//	@return errRtn
func getIDs(tblName, columnID string) (ids []string, errRtn error) {
	var entity zd.IEntity
	entity, ok := tblnameUpperEntity[tblName]
	if !ok {
		errRtn = fmt.Errorf("无效表名: %s", tblName)
		return
	}
	mps, err := zd.SelectMapColumnsWithAppend(ctxDao, nil, nil, []string{fmt.Sprintf("Distinct %s %s", columnID, columnID)}, "and not ISNULL("+columnID+")", entity)
	if err != nil {
		errRtn = err
	}
	for _, mp := range mps {
		for _, id := range mp {
			ids = append(ids, id.(string))
		}
	}
	return
}

// get 查询数据
func get(ctx *gin.Context, entity zd.IEntity) (data any, total int, errRtn error) {
	var (
		qry       map[string]string
		cur, size int
		append    string
		p         *zd.PageInfo = nil
		where     map[string]any
	)
	tblName := ctx.Param("tblName")
	qry, cur, size, append, errRtn = ge.QryParams(ctx)
	if errRtn != nil {
		errRtn = errors.Wrap(errRtn, "查询参数")
		return
	}
	if cur > 0 {
		p = &zd.PageInfo{PageNo: cur, PageSize: size}
	}

	if where, errRtn = zd.FixQryParams(ctxDao, entity, qry); errRtn != nil {
		return
	}
	data, errRtn = zd.SelectMapWithAppend(ctxDao, p, where, append, entity)
	if errRtn != nil {
		errRtn = errors.Wrap(errRtn, "查询 "+tblName)
	}
	if p != nil {
		total = p.TotalCount
	}
	return
}

// postGet Post查询参数
func postGet(ctx *gin.Context, entity zd.IEntity) (data any, total int, errRtn error) {
	var (
		cur, size int
		append    string
		p         *zd.PageInfo = nil
		where     map[string]any
	)
	where, cur, size, append, errRtn = ge.PostGetParams(ctx)
	if errRtn != nil {
		errRtn = errors.Wrap(errRtn, "查询参数")
		return
	}
	if cur > 0 {
		p = &zd.PageInfo{PageNo: cur, PageSize: size}
	}

	data, errRtn = zd.SelectMapWithAppend(ctxDao, p, where, append, entity)
	if p != nil {
		total = p.TotalCount
	}
	if errRtn != nil {
		errRtn = errors.Wrap(errRtn, "查询 "+zd.GetTableName(ctxDao, entity))
	}
	return
}

func tblExists(tblName string) (exists bool) {
	var mps []map[string]any
	var err error
	if ctxDao.Value(zd.PG) != nil {
		tblName = strings.ToLower(tblName) //
		mps, err = zd.QueryMap(ctxDao, fmt.Sprintf("SELECT COUNT(1) FROM information_schema.tables WHERE table_schema='%s' and table_type='BASE TABLE' AND table_name='%s'", strings.Split(tblName, ".")[0], strings.Split(tblName, ".")[1]))
	} else {
		tblName = strings.ToUpper(tblName)  //
		if ctxDao.Value(zd.ORACLE) != nil { // oracle
			mps, err = zd.QueryMap(ctxDao, fmt.Sprintf("SELECT COUNT(1) FROM ALL_TABLES WHERE owner='%s' AND table_name='%s'", strings.Split(tblName, ".")[0], strings.Split(tblName, ".")[1]))
		} else { // 达梦
			mps, err = zd.QueryMap(ctxDao, fmt.Sprintf("SELECT COUNT(1) FROM dba_segments WHERE owner='%s' AND segment_name='%s'", strings.Split(tblName, ".")[0], strings.Split(tblName, ".")[1]))
		}
	}
	if err != nil {
		return false
	}
	for _, v := range mps[0] {
		if n, _ := strconv.Atoi(fmt.Sprintf("%v", v)); n > 0 {
			return true
		}
	}
	return false
}

// func post 插入数据
func post(ctx *gin.Context, entity zd.IEntity) (errRtn error) {
	params, err := ge.PostParams(ctx)
	if err != nil {
		errRtn = errors.Wrap(err, "取Post参数")
		return
	}
	// 判断 entity 是否有 InsertTime 属性
	if val := reflect.ValueOf(entity).Elem().FieldByName("InsertTime"); val.IsValid() {
		params["InsertTime"] = time.Now().Local().Format(time.DateTime)
	}
	if val := reflect.ValueOf(entity).Elem().FieldByName("UpdateTime"); val.IsValid() {
		params["UpdateTime"] = time.Now().Local().Format(time.DateTime)
	}

	tblName := zd.GetTableName(ctxDao, entity)
	n, err := zd.InsertMap(ctxDao, params, entity)
	if err != nil {
		errRtn = errors.Wrap(err, "插入 "+tblName)
		return
	}

	logrus.Infof("插入 %s: %d条", tblName, n)
	// 日志入库
	if tblExists(tblName + "log") {
		params["LogTime"] = time.Now().Local().Format(time.DateTime)
		claims, _ := GetClaims(ctx)
		if claims != nil {
			params["OperateUser"] = claims.User.Name
		}
		errRtn = zd.InsertMapByName(ctxDao, tblName+"log", params)
		if errRtn == nil {
			logrus.Infof("日志 %s 入库 1 条添加", tblName+"log")
		}
	}
	return
}

// func put 更新数据
func put(ctx *gin.Context, entity zd.IEntity) (errRtn error) {
	where, set, err := ge.PutParams(ctx)
	if err != nil {
		errRtn = errors.Wrap(err, "取Put参数")
		return
	}
	if len(where) == 0 || len(set) == 0 {
		errRtn = errors.New("where/set 为空")
		return
	}
	// 先查出来以写入日志, 避免 where中的字段被修改后查不到数据的问题
	mpID, err := zd.SelectMapColumns(ctxDao, nil, where, []string{"UUID"}, entity)
	if err != nil {
		errRtn = errors.Wrap(err, "查ID错误")
		return
	}
	if len(mpID) == 0 {
		errRtn = fmt.Errorf("没有符合条件的数据 %v", where)
		return
	}
	setJson, _ := json.Marshal(set) // log 中保存更新内容
	// 判断 entity 是否有 UpdateTime 属性
	if val := reflect.ValueOf(entity).Elem().FieldByName("UpdateTime"); val.IsValid() {
		set["UpdateTime"] = time.Now().Local().Format(time.DateTime)
	}
	delete(set, "InsertTime")

	n := 0
	n, errRtn = zd.UpdateMap(ctxDao, where, set, entity)
	if errRtn != nil {
		return
	}
	tblName := zd.GetTableName(ctxDao, entity)
	logrus.Infof("更新 %s 数据 %d 条", tblName, n)
	// 日志入库
	if tblExists(tblName + "log") {
		ids := []any{}
		for _, mp := range mpID {
			for _, id := range mp {
				ids = append(ids, id)
			}
		}
		// 查出原表中的内容
		rows, err := zd.SelectMap(ctxDao, nil, map[string]any{"UUID": ids}, entity)
		if err != nil {
			errRtn = errors.Wrap(err, "SelectMapColumnsByName")
		} else if len(rows) > 0 {
			for _, row := range rows {
				row["UpdateContent"] = string(setJson)
				row["LogTime"] = time.Now().Local().Format(time.DateTime)
				claims, _ := GetClaims(ctx)
				if claims != nil {
					row["OperateUser"] = claims.User.Name
				}
			}
			// 日志入库
			errRtn = zd.InsertMapByName(ctxDao, tblName+"log", rows...)
			// n, errRtn = zd.InsertMaps(ctxDao, rows, entityLog)
			if errRtn == nil {
				logrus.Infof("日志 %sLog 入库 %d 条更新", tblName, n)
			}
		}
	}
	return
}

// func imp 导入数据
func imp(ctx *gin.Context, entity zd.IEntity) (errRtn error) {
	var records []map[string]any
	errRtn = ctx.ShouldBindJSON(&records)
	if errRtn != nil {
		return
	}
	// 设置插入时间
	if val := reflect.ValueOf(entity).Elem().FieldByName("InsertTime"); val.IsValid() {
		now := time.Now().Local().Format(time.DateTime)
		for _, record := range records {
			// 数据中不包含插入时间，则设置
			if _, ok := record["InsertTime"]; !ok {
				record["InsertTime"] = now
			}
		}
	}
	// 设置更新时间
	if val := reflect.ValueOf(entity).Elem().FieldByName("UpdateTime"); val.IsValid() {
		now := time.Now().Local().Format(time.DateTime)
		for _, record := range records {
			// 数据中不包含更新时间，则设置
			if _, ok := record["UpdateTime"]; !ok {
				record["UpdateTime"] = now
			}
		}
	}
	_, errRtn = zd.InsertMaps(ctxDao, records, entity)
	return
}

func del(ctx *gin.Context, entity zd.IEntity) (errRtn error) {
	where, err := ge.PostParams(ctx)
	if err != nil {
		errRtn = errors.Wrap(err, "PostParams")
		return
	}
	rows, err := zd.SelectMap(ctxDao, nil, where, entity)
	if err != nil {
		errRtn = errors.Wrap(err, "SelectMap")
		return
	}
	if len(rows) == 0 {
		errRtn = errors.New("未找到数据")
		return
	}
	var n int
	n, errRtn = zd.Delete(ctxDao, where, entity)
	if errRtn != nil {
		return
	}
	tblName := zd.GetTableName(ctxDao, entity)
	logrus.Infof("删除 %s: %d 条", tblName, n)
	// 日志入库
	if tblExists(tblName + "log") {
		for _, row := range rows {
			row["UpdateContent"] = "Delete"
			row["LogTime"] = time.Now().Local().Format(time.DateTime)
			claims, _ := GetClaims(ctx)
			if claims != nil {
				row["OperateUser"] = claims.User.Name
			}
		}
		errRtn = zd.InsertMapByName(ctxDao, tblName+"log", rows...)
		if errRtn == nil {
			logrus.Infof("日志 %s 入库 %d 条", tblName+"log", n)
		}
	}
	return
}

// process 处理请求
//
//	@param ctx
//	@return data
//	@return total
//	@return errRtn
func process(ctx *gin.Context) (data any, total int, errRtn error) {
	tblName := ctx.Param("tblName")
	columnID := ctx.Param("columnID")
	var GET, POST, PUT, DEL, POSTGet, POSTImp, GETIDs bool
	switch ctx.Request.Method {
	case "GET":
		if len(columnID) > 0 { // 取 ID 列表
			GETIDs = true
		} else {
			GET = true
		}
	case "POST":
		if strings.Contains(ctx.Request.URL.Path, "/get/"+tblName) { // post 方式取数据
			POSTGet = true
		} else if strings.Contains(ctx.Request.URL.Path, "/imp/"+tblName) { // 导入 excel
			POSTImp = true
		} else {
			POST = true
		}
	case "PUT":
		PUT = true
	case "DELETE":
		DEL = true
	}

	tblName = strings.ToUpper(tblName) // 转为大写, 以便从 tblnameUpperEntity 中取值
	if GETIDs {
		data, errRtn = getIDs(tblName, columnID)
		return
	}

	// 不区分 tblName 大小写, 从 tblEntity 中获取 tblName 对应的实体
	entity, ok := tblnameUpperEntity[tblName]
	if !ok {
		errRtn = errors.New("tblName 不存在")
		return
	}
	if GET {
		data, total, errRtn = get(ctx, entity)
	} else if POST {
		errRtn = post(ctx, entity)
	} else if PUT {
		errRtn = put(ctx, entity)
	} else if DEL {
		errRtn = del(ctx, entity)
	} else if POSTGet {
		data, total, errRtn = postGet(ctx, entity)
	} else if POSTImp {
		errRtn = imp(ctx, entity)
	}

	return
}

// RouteTable 初始化路由
//
// 先对 TblEntity 初始化
//
//	@param g gin.RouterGroup 路由组
//	@param tblnameEntity 表名->实体
//	@param viewEntity 视图名->实体(不会建表)
//	@param ctxDB
func RouteTable(g *gin.RouterGroup, tblnameEntity, viewEntity map[string]zd.IEntity, ctxDB context.Context) {
	for k, v := range tblnameEntity {
		tblnameUpperEntity[strings.ToUpper(k)] = v
	}
	ctxDao = ctxDB
	for _, v := range tblnameUpperEntity {
		err := zd.CreateTable(ctxDao, v)
		if err != nil {
			logrus.Fatal(err)
		}
	}
	for k, v := range viewEntity {
		tblnameUpperEntity[strings.ToUpper(k)] = v
	}

	// ID 列表
	g.GET("/:tblName/:columnID", ge.CreateQryHandler(process))

	// 取数据
	g.GET("/:tblName", ge.CreateQryHandler(process))

	// POST 方式查询
	g.POST("/get/:tblName", ge.CreateQryHandler(process))

	// 新增
	g.POST("/:tblName", ge.CreateUpHandler(func(ctx *gin.Context) (errRtn error) {
		_, _, errRtn = process(ctx)
		return
	}))

	// 批量插入
	g.POST("/imp/:tblName", ge.CreateUpHandler(func(ctx *gin.Context) (errRtn error) {
		_, _, errRtn = process(ctx)
		return
	}))

	// 更新
	g.PUT("/:tblName", ge.CreateUpHandler(func(ctx *gin.Context) (errRtn error) {
		_, _, errRtn = process(ctx)
		return
	}))

	// 删除
	g.DELETE("/:tblName", ge.CreateUpHandler(func(ctx *gin.Context) (errRtn error) {
		_, _, errRtn = process(ctx)
		return
	}))
}
